'use strict';

class Captcha {
    root = null;
    version = '1.0.0';

    // 需要滑动滑块的组件，需要注册滑动松手后的回调事件
    #onDraggerMoving = null;
    #onSubmit = null;
    #onClickSubmit = null;

    // 鼠标按下事件
    #mouseDownEvent = null;

    constructor() {
        this.root = this.getRootNode();
    }

    getRootNode() {
        const root = document.createElement('div');
        root.id = 'captcha';
        root.innerHTML = `<div class="pi-captcha">
            <div class="pi-captcha-loading">
                <svg class="pi-captcha-loading-icon" xmlns="http://www.w3.org/2000/svg" width="60" height="60" viewBox="0 0 24 24"><circle cx="18" cy="12" r="0" fill="currentColor"><animate attributeName="r" begin=".67" calcMode="spline" dur="1.5s" keySplines="0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8" repeatCount="indefinite" values="0;2;0;0"/></circle><circle cx="12" cy="12" r="0" fill="currentColor"><animate attributeName="r" begin=".33" calcMode="spline" dur="1.5s" keySplines="0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8" repeatCount="indefinite" values="0;2;0;0"/></circle><circle cx="6" cy="12" r="0" fill="currentColor"><animate attributeName="r" begin="0" calcMode="spline" dur="1.5s" keySplines="0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8" repeatCount="indefinite" values="0;2;0;0"/></circle></svg>
                <div class="pi-captcha-success">
                    <svg xmlns="http://www.w3.org/2000/svg" width="60" height="60" viewBox="0 0 32 32"><path fill="currentColor" d="M22.8 12.3c.304-.308.304-.808 0-1.12s-.796-.308-1.1 0l-7.75 7.86l-3.6-3.65a.77.77 0 0 0-1.1 0a.8.8 0 0 0 0 1.12l4.15 4.21a.77.77 0 0 0 1.1 0z"/><path fill="currentColor" fill-rule="evenodd" d="M32 16c0 8.84-7.16 16-16 16S0 24.84 0 16S7.16 0 16 0s16 7.16 16 16m-1 0c0 8.28-6.72 15-15 15S1 24.28 1 16S7.72 1 16 1s15 6.72 15 15" clip-rule="evenodd"/></svg>
                    <div>验证成功</div>
                </div>
            </div>
            <div class="pi-captcha-container" style="display:none">
                <div class="pi-captcha-header">
                    <div>安全验证</div>
                    <div>
                        <svg class="pi-captcha-close" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><path fill="currentColor" d="M16 2C8.2 2 2 8.2 2 16s6.2 14 14 14s14-6.2 14-14S23.8 2 16 2m0 26C9.4 28 4 22.6 4 16S9.4 4 16 4s12 5.4 12 12s-5.4 12-12 12"/><path fill="currentColor" d="M21.4 23L16 17.6L10.6 23L9 21.4l5.4-5.4L9 10.6L10.6 9l5.4 5.4L21.4 9l1.6 1.6l-5.4 5.4l5.4 5.4z"/></svg>
                    </div>
                </div>
                <div class="pi-captcha-body">
                    <div class="pi-captcha-title">请依次点击下图中的字符</div>
                    <div class="pi-captcha-image">
                        <div class="pi-captcha-refer" id="slideBg" style="--decoration-height:220px;--decoration-image:url(https://pic35.photophoto.cn/20150511/0034034892281415_b.jpg)"></div>
                        <div class="pi-captcha-set"></div>
                    </div>
                    <div class="pi-captcha-input">
                        <div class="pi-captcha-input-draggable">
                            <div class="pi-captcha-input-item">
                                <div class="pi-captcha-input-item-helper">请拖动滑块完成验证</div>
                            </div>
                            <div class="pi-captcha-input-drag" style="--x:0">
                                <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M5.7 6.71a.996.996 0 0 0 0 1.41L9.58 12L5.7 15.88a.996.996 0 1 0 1.41 1.41l4.59-4.59a.996.996 0 0 0 0-1.41L7.12 6.71c-.39-.39-1.03-.39-1.42 0"/><path fill="currentColor" d="M12.29 6.71a.996.996 0 0 0 0 1.41L16.17 12l-3.88 3.88a.996.996 0 1 0 1.41 1.41l4.59-4.59a.996.996 0 0 0 0-1.41L13.7 6.7c-.38-.38-1.02-.38-1.41.01"/></svg>
                            </div>
                        </div>
                    </div>
                </div>
                <div class="pi-captcha-footer">
                    <div>
                        <svg class="pi-captcha-refresh" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M12 20q-3.35 0-5.675-2.325T4 12t2.325-5.675T12 4q1.725 0 3.3.712T18 6.75V5q0-.425.288-.712T19 4t.713.288T20 5v5q0 .425-.288.713T19 11h-5q-.425 0-.712-.288T13 10t.288-.712T14 9h3.2q-.8-1.4-2.187-2.2T12 6Q9.5 6 7.75 7.75T6 12t1.75 4.25T12 18q1.7 0 3.113-.862t2.187-2.313q.2-.35.563-.487t.737-.013q.4.125.575.525t-.025.75q-1.025 2-2.925 3.2T12 20"/></svg>
                    </div>
                    <div>
                        <div class="pi-captcha-submit">确定</div>
                    </div>
                </div>
            </div>
        </div>`;

        root.querySelector('.pi-captcha-close').addEventListener('click', () => {
            this.destroy();
        });

        root.querySelector('.pi-captcha-submit').addEventListener('click', () => {
            this.#onClickSubmit();
        });

        return root;
    }

    loadContent(node = document.body) {
        node.appendChild(this.root);

        return this.#rerender();
    }

    #rerender() {
        return this.getCaptchaContent().then(([type, data]) => {
            return this.renderComponent(type, data);
        });
    }

    #completeOperation(...data) {
        this.openLoading();

        return this.#onSubmit(...data)
            .then(() => {
                this.root.querySelector('.pi-captcha-loading').classList.add('pi-captcha-loading-success');
            })
            .catch((err) => {
                this.closeLoading();
                this.boardShake();

                setTimeout(() => {
                    this.#rerender();
                }, 500);
            });
    }

    #onDraggerDown(e) {
        const drag = this.root.querySelector('.pi-captcha-input-drag');
        
        if (!drag.contains(e.target)) {
            return;
        }

        let percent = 0;
        const times = [Date.now()];
        const startX = e.x;
        const width = drag.offsetWidth;
        const parentWidth = drag.parentNode.offsetWidth;
        const max = parentWidth - width - 2;
        
        const onMove = ((e) => {
            const moved = Math.min(parentWidth - width - 2, Math.max(0, e.x - startX));
            percent = moved / max;

            drag.style.setProperty('--x', `${moved}px`);
            this.#onDraggerMoving(moved, percent);
        }).bind(this);

        const onUp = (() => {
            window.removeEventListener('mousemove', onMove);
            window.removeEventListener('mouseup', onUp);
            times.push(Date.now());

            return this.#completeOperation(percent, times);
        }).bind(this);

        window.addEventListener('mousemove', onMove);
        window.addEventListener('mouseup', onUp);
    };

    openLoading() {
        this.root.querySelector('.pi-captcha-loading').style.setProperty('display', '');
    }

    closeLoading() {
        this.root.querySelector('.pi-captcha-loading').style.setProperty('display', 'none');
    }

    openBoard() {
        this.root.querySelector('.pi-captcha-container').style.setProperty('display', 'block');
    }

    closeBoard() {
        this.root.querySelector('.pi-captcha-container').style.setProperty('display', 'none');
    }

    showSubmit() {
        this.root.querySelector('.pi-captcha-submit').style.setProperty('display', 'block');
    }

    hideSubmit() {
        this.root.querySelector('.pi-captcha-submit').style.setProperty('display', 'none');
    }

    /**
     * 打开拖拽行
     */
    openDragger() {
        this.root.querySelector('.pi-captcha-input').style.setProperty('display', 'block');

        window.removeEventListener('mousedown', this.#mouseDownEvent);
        
        this.#mouseDownEvent = this.#onDraggerDown.bind(this);
        window.addEventListener('mousedown', this.#mouseDownEvent);
    }

    resetDragger() {
        const drag = this.root.querySelector('.pi-captcha-input-drag');

        drag.style.setProperty('transition', `cubic-bezier(0.075, 0.82, 0.165, 1) 0.4s`);
        drag.style.setProperty('--x', `0`);
        setTimeout(() => {
            drag.style.setProperty('transition', `none`);
            this.root.querySelector('.pi-captcha-refer').classList.remove('shake');
        }, 400);
    }

    boardShake() {
        this.root.querySelector('.pi-captcha-refer').classList.add('shake');

        setTimeout(() => {
            this.root.querySelector('.pi-captcha-refer').classList.remove('shake');
        }, 1000);
    }

    closeDragger() {
        window.removeEventListener('mousedown', this.#mouseDownEvent);
        this.root.querySelector('.pi-captcha-input').style.setProperty('display', 'none');
    }

    /**
     * 注册组件
     */
    renderComponent(type, data) {
        this.root.querySelector('.pi-captcha-set').innerHTML = '';

        switch(type) {
            case '1': return this.renderDraggerComponent(data);
            case '2': return this.renderRotaterComponent(data);
            case '3': return this.renderClickerComponent(data);
        }
    }

    // 滑动
    renderDraggerComponent(data) {
        return new Promise((resolve) => {
            this.closeLoading();
            this.openBoard();
            this.openDragger();
            this.hideSubmit();

            const imgDrag = document.createElement('div');
            imgDrag.classList.add('pi-captcha-set-drag');
            imgDrag.style.setProperty('--x', `0`);
            this.root.querySelector('.pi-captcha-set').appendChild(imgDrag);

            this.#onDraggerMoving = (moved, percent) => {
                imgDrag.style.setProperty('--x', `${moved}px`);
            };
            this.#onSubmit = (percent, times) => {
                return this.validCaptcha(data.type, percent, times)
                    .then((code) => {
                        this.root.querySelector('.pi-captcha-loading').classList.add('pi-captcha-loading-success');
                        this.destroy();
                        setTimeout(() => {
                            resolve(code);
                        }, 500);
                    })
                    .catch((err) => {
                        this.resetDragger();
                        return Promise.reject(err);
                    });
            };
        })
    }

    // 滑动旋转
    renderRotaterComponent(data) {
        return new Promise((resolve) => {
            this.closeLoading();
            this.openBoard();
            this.openDragger();
            this.hideSubmit();

            const imgRotate = document.createElement('div');
            imgRotate.classList.add('pi-captcha-set-rotate');
            imgRotate.style.setProperty('--r', `0`);
            this.root.querySelector('.pi-captcha-set').appendChild(imgRotate);

            this.#onDraggerMoving = (moved, percent) => {
                imgRotate.style.setProperty('--r', `${percent * 360}deg`);
            };
            this.#onSubmit = (percent, times) => {
                return this.validCaptcha(data.type, percent, times)
                    .then((code) => {
                        this.root.querySelector('.pi-captcha-loading').classList.add('pi-captcha-loading-success');
                        this.destroy();
                        setTimeout(() => {
                            resolve(code);
                        }, 500);
                    })
                    .catch((err) => {
                        this.resetDragger();
                        return Promise.reject(err);
                    });
            };
        });
    }

    // 点击位置
    renderClickerComponent(data) {
        return new Promise((resolve) => {
            this.closeLoading();
            this.openBoard();
            this.closeDragger();
            this.showSubmit();

            const clickBoard = document.createElement('div');
            clickBoard.classList.add('pi-captcha-set-click');
            this.root.querySelector('.pi-captcha-set').appendChild(clickBoard);
            let index = 1;
            const clickers = [];

            const createClicker = (x, y, index) => {
                const clicker = document.createElement('div');
                clicker.classList.add('pi-captcha-set-clicker');
                clicker.style.setProperty('--x', `${x * 100}%`);
                clicker.style.setProperty('--y', `${y * 100}%`);
                clicker.setAttribute('data-index', index);
                clickBoard.appendChild(clicker);
                clickers.push([x, y, index]);
            };

            clickBoard.addEventListener('click', (e) => {
                // 获取点击位置的x和y和clickBoard的宽高
                const rect = e.target.getBoundingClientRect();
                const x = e.clientX - rect.left;
                const y = e.clientY - rect.top;
                const width = rect.width;
                const height = rect.height;

                // 计算点击位置的百分比
                createClicker(x / width, y / height, index);
                index += 1;
            });

            this.#onSubmit = () => {
                return this.validCaptcha(data.type, clickers)
                    .then((code) => {
                        this.root.querySelector('.pi-captcha-loading').classList.add('pi-captcha-loading-success');
                        this.destroy();
                        setTimeout(() => {
                            resolve(code);
                        }, 500);
                    })
                    .catch((err) => {
                        this.resetDragger();
                        return Promise.reject(err);
                    });
            }

            this.#onClickSubmit = () => {
                this.#completeOperation();
            }

        });
    }

    async getCaptchaContent() {
        await new Promise((resolve) => {
            this.openLoading();

            setTimeout(() => {
                resolve();
            }, 2000);
        });

        return ['3', {
            type: '3',
            bg: '',
            trigger: ''
        }];
    }

    validCaptcha(type, percent, times) {
        return new Promise((resolve, reject) => {
            console.log(type, percent, times);
            setTimeout(() => {
                reject('123456789');
            }, 1000);
        });
    }

    // 销毁
    destroy() {
        this.closeDragger();
        this.root.remove();
    }
};
